﻿using System.Text.RegularExpressions;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System;
using System.Xml.Linq;

namespace AL.Doodled.Compiling
{
    public class Compiler
    {
        #region fields
        private DoodledEngine engine;
        private WidgetCompiler widgetCompiler;
        #endregion

        #region initialization
        public Compiler(DoodledEngine engine)
        {
            this.widgetCompiler = new WidgetCompiler(engine);
            this.engine = engine;
        }
        #endregion

        #region resourceData
        /// <summary>
        /// Takes a resource data string and add its information
        /// to the engines information.
        /// </summary>
        /// <param name="data">The data string.</param>
        public void ParseResourceData(string data, ContentManager content, 
                                             GraphicsDevice gDevice)
        {
            textureAdding(data, content, gDevice);
            fontAdding(data, content);
        }

        /// <summary>
        /// Finds all texture information and adds it to the engine.
        /// </summary>
        private void textureAdding(string data, ContentManager content, GraphicsDevice gDevice)
        {
            Match textureMatch = Regex.Match(data, @"textures\s*\{([^}]*)");
            if (textureMatch.Success)
            {
                foreach (Match match in Regex.Matches(
                    textureMatch.Groups[1].Value, @"([^\s]*)\s*\=\s*([^\s]*)"))
                {
                    string target = match.Groups[2].Value.Replace("@", "");
                    bool isPath = target.Length != match.Groups[1].Value.Length;

                    if (!isPath)
                        engine.SetTexture(match.Groups[1].Value,
                            content.Load<Texture2D>(target));
                    else
                        engine.SetTexture(match.Groups[1].Value,
                            Texture2D.FromStream(gDevice,
                            new System.IO.FileStream(target,
                                System.IO.FileMode.Open)));
                }
            }
        }

        /// <summary>
        /// Finds all font information and adds it to the engine.
        /// </summary>
        private void fontAdding(string data, ContentManager content)
        {
            Match fontMatch = Regex.Match(data, @"fonts\s*\{([^}]*)");
            if (fontMatch.Success)
            {
                foreach (Match match in Regex.Matches(
                    fontMatch.Groups[1].Value, @"([^\s]*)\s*\=\s*([^\s]*)"))
                {
                    string target = match.Groups[2].Value;

                    engine.SetFont(match.Groups[1].Value,
                        content.Load<SpriteFont>(target));
                }
            }
        }
        #endregion

        #region registerData
        /// <summary>
        /// Takes a register data string and converts it into engine globals.
        /// </summary>
        /// <param name="data">The register data string.</param>
        public void ParseRegisterData(string data)
        {
            foreach (Match match in Regex.Matches(data, @"([^\s]+)\s*:\s*'([^']*)"))
            {
                engine.SetGlobal(match.Groups[1].Value, match.Groups[2].Value);
            }

            foreach (Match match in Regex.Matches(data, "([^\\s]+)\\s*:\\s*\"([^\"]*)"))
            {
                engine.SetGlobal(match.Groups[1].Value, match.Groups[2].Value);
            }
        }

        /// <summary>
        /// Takes a string and replace all global occurences 
        /// with the globals data.
        /// </summary>
        /// <param name="text">The text to scann.</param>
        /// <returns>
        /// A new string instance containing
        /// the altered text.
        /// </returns>
        public string RegisterCompile(string text)
        {
            string result = text;

            foreach(Match match in Regex.Matches(text, @"$([^\s]*)"))
            {
                string key = match.Groups[1].Value;
                if(engine.IsGlobalDefined(key))
                result = result.Replace(key, engine.GetGlobalData(key));
            }

            return result;
        }
        #endregion

        #region styleData
        /// <summary>
        /// Extract all attributes from styleCode.
        /// </summary>
        /// <param name="styleCode">The style code.</param>
        /// <returns>The attributes.</returns>
        public Dictionary<string, string> ExtractStyleAttributes(string styleCode)
        {
            Dictionary<string, string> attributes = new Dictionary<string, string>();

            foreach (Match match in Regex.Matches(styleCode, @"([^\s]+)\s*:\s*([^;]+)"))
            {
                string value = match.Groups[2].Value.Trim();
                if (attributes.ContainsKey(match.Groups[1].Value))
                    attributes[match.Groups[1].Value] = value;
                else
                    attributes.Add(match.Groups[1].Value, value);
            }

            return attributes;
        }

        public Style ParseStyleData(string styleCode)
        {
            string cleaned = styleCode;
            Style style = new Style();

            foreach (Match match in Regex.Matches(styleCode, @"§([\w]+)\s*\{([^}]*)"))
            {
                try
                {
                    StyleState state = new StyleState();
                    foreach (var attribute in ExtractStyleAttributes(match.Groups[2].Value))
                        state.AddAttribute(attribute.Key, attribute.Value);

                    style.AddState(match.Groups[1].Value, state);
                }
                catch(Exception e)
                {
                    throw new Exception("There was an error when parseing the style code: " + e);
                }
                finally
                {
                    cleaned = cleaned.Replace(match.Groups[0].Value, "");
                }
            }

            foreach (var attribute in ExtractStyleAttributes(cleaned))
                style.GetDefaultState().AddAttribute(attribute.Key,
                    attribute.Value);

            return style;
        }
        #endregion

        #region widget
        public Widget CompileWidget(XElement widgetElement)
        {
            return widgetCompiler.Compile(widgetElement);
        }
        #endregion
    }
}
